package com.cloudfun.campusshare.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.cloudfun.campusshare.common.constant.Code;
import com.cloudfun.campusshare.common.constant.ProductType;
import com.cloudfun.campusshare.common.constant.WashPointType;
import com.cloudfun.campusshare.common.exception.BusinessException;
import com.cloudfun.campusshare.common.model.vo.*;
import com.cloudfun.campusshare.common.response.BaseResult;
import com.cloudfun.campusshare.common.response.BaseResultFactory;
import com.cloudfun.campusshare.common.response.PageResult;
import com.cloudfun.campusshare.common.security.AuthenticationHolder;
import com.cloudfun.campusshare.common.security.UserAuthentication;
import com.cloudfun.campusshare.entity.UserEntity;
import com.cloudfun.campusshare.entity.WashPointConcretePositionEntity;
import com.cloudfun.campusshare.entity.WashPointEntity;
import com.cloudfun.campusshare.repo.*;
import com.cloudfun.campusshare.service.*;
import com.cloudfun.campusshare.util.AmountUtil;
import com.cloudfun.campusshare.util.BeanCopyUtil;
import com.cloudfun.campusshare.util.GeoDisComputeUtil;
import com.cloudfun.campusshare.util.QueryUtil;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.sql.Select;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import javax.persistence.criteria.Predicate;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author : zhangyongjie
 * @version : 1.0
 * @createTime : 2020/4/28 19:27
 * @description :
 */
@Service
@Slf4j
public class WashPointServiceImpl extends CreatorPersistenceService implements WashPointService {

    @Resource
    private WashPointEntityRepo washPointEntityRepo;

    @Resource
    private WashPointConcretePositionEntityRepo washPointConcretePositionEntityRepo;

    @Resource
    private UserEntityRepo userEntityRepo;

    @Resource
    private DeviceEntityRepo deviceEntityRepo;

    @Autowired
    private UserRelService userRelService;

    @Resource
    private EntityManager entityManager;

    @Autowired
    private UserPreferWashPointService userPreferWashPointService;
    @Autowired
    private DeviceService deviceService;
    @Autowired
    private DeviceServiceTimeEntityRepo deviceServiceTimeEntityRepo;

    @Override
    protected Class<?> getEntityClass() {
        return WashPointEntity.class;
    }

    @Override
    @Transactional
    public void addWashPoint(WashPointAddVO washPointAddVO) {
        //运营商是根帐号id
        UserEntity merchant = userRelService.getUserMerchant(AuthenticationHolder.getUserId());

        if (StringUtils.isBlank(washPointAddVO.getId())) {
            WashPointEntity entity = BeanCopyUtil.copy(washPointAddVO, WashPointEntity.class);
            entity.setMerchantId(merchant.getId());
            entity.setBelongMerchantName(merchant.getEnterpriseName());
            WashPointEntity save = washPointEntityRepo.save(entity);
            //如果具体地址不为空
            if (!CollectionUtils.isEmpty(washPointAddVO.getWashPointList())) {
                washPointConcretePositionEntityRepo.saveAll(washPointAddVO.getWashPointList().stream().filter(x -> !(StringUtils.isBlank(x.getBuilding()) &&
                        StringUtils.isBlank(x.getFloor()) &&
                        StringUtils.isBlank(x.getRoom()))).map(
                        x -> {
                            WashPointConcretePositionEntity wpc = BeanCopyUtil.copy(x, WashPointConcretePositionEntity.class);
                            wpc.setWashPointId(save.getId());
                            return wpc;
                        }
                ).collect(Collectors.toList()));
            }
        } else {
            //更新操作
            Optional<WashPointEntity> was = washPointEntityRepo.findById(washPointAddVO.getId());
            if (!was.isPresent()) {
                throw new BusinessException(Code.ERROR, "没有该洗衣点,id:" + washPointAddVO.getId());
            }
            WashPointEntity washPointEntity = was.get();
            washPointEntity.setName(washPointAddVO.getName());
            washPointEntity.setArea(washPointAddVO.getArea());
            washPointEntity.setDetailAddress(washPointAddVO.getDetailAddress());
            washPointEntity.setLatitude(washPointAddVO.getLatitude());
            washPointEntity.setLongitude(washPointAddVO.getLongitude());
            washPointEntity.setServicePhone(washPointAddVO.getServicePhone());
            //更新
            washPointEntityRepo.save(washPointEntity);

            List<WashPointConcretePositionEntity> existList = washPointConcretePositionEntityRepo.findAllByWashPointId(washPointAddVO.getId());

            if (!CollectionUtils.isEmpty(washPointAddVO.getWashPointList())) {
                List<String> collect = existList.stream().map(x -> x.getId())
                        .filter(x -> !washPointAddVO.getWashPointList().stream().map(x1 -> x1.getId()).filter(x1 -> StringUtils.isNotBlank(x1))
                        .collect(Collectors.toList()).contains(x)).collect(Collectors.toList());

                if (CollectionUtil.isNotEmpty(deviceEntityRepo.findAllByConcretePositionIdIn(collect))) {
                    throw new BusinessException(Code.ERROR, "有具体位置存在绑定的设备");
                }
                washPointConcretePositionEntityRepo.deleteAllByIdIn(collect);

                washPointAddVO.getWashPointList().stream().filter(x -> StringUtils.isNotBlank(x.getId())).forEach(x -> {
                    if(StringUtils.isNotBlank(x.getBuilding()) && StringUtils.isNotBlank(x.getFloor()) && StringUtils.isNotBlank(x.getRoom())){
                        WashPointConcretePositionEntity concretePositionEntity = washPointConcretePositionEntityRepo.findById(x.getId()).orElseThrow(() -> new BusinessException(Code.NODATA));
                        concretePositionEntity.setBuilding(x.getBuilding());
                        concretePositionEntity.setFloor(x.getFloor());
                        concretePositionEntity.setRoom(x.getRoom());
                        washPointConcretePositionEntityRepo.save(concretePositionEntity);
                    }
                });

                List<WashPointConcretePositionEntity> inserts = washPointAddVO.getWashPointList().stream().filter(x -> StringUtils.isBlank(x.getId())).map(
                        x -> {
                            WashPointConcretePositionEntity wpc = BeanCopyUtil.copy(x, WashPointConcretePositionEntity.class);
                            wpc.setWashPointId(washPointEntity.getId());
                            return wpc;
                        }
                ).collect(Collectors.toList());
                washPointConcretePositionEntityRepo.saveAll(inserts);
            } else {
                List<String> collect = existList.stream().map(x -> x.getId()).collect(Collectors.toList());
                if (CollectionUtil.isNotEmpty(deviceEntityRepo.findAllByConcretePositionIdIn(collect))) {
                    throw new BusinessException(Code.ERROR, "有具体位置存在绑定的服务时间或者设备");
                }
                washPointConcretePositionEntityRepo.deleteAllByWashPointId(washPointEntity.getId());
            }
        }
    }

    @Override
    public PageResult queryWashPointList(Pageable pageable, String keyword, Set<WashPointType> type, String operatorId) {
        Page<WashPointEntity> page = washPointEntityRepo.findAll(assembleSpecification(keyword, operatorId), pageable);
        List<WashPointEntity> content = page.getContent();
        List<WashPointListVO> collect = content.stream().map(x -> {
            WashPointListVO copy = BeanCopyUtil.copy(x, WashPointListVO.class);
            Long deviceCount = deviceEntityRepo.countByWashPointId(x.getId());
            copy.setDeviceCount(deviceCount);
            copy.setMonthGain(computeMonthGain(x.getId()));
            return copy;
        }).collect(Collectors.toList());

        PageResult pg = new PageResult(page.getTotalElements(), collect);
        return pg;
    }

    @Override
    public List<WashPointForQueryListVO> getWashPointList(String keyword, String operatorId) {
        return washPointEntityRepo.findAll(assembleSpecification(keyword, operatorId)).stream().map(x -> {
            WashPointForQueryListVO washPointForQueryListVO = new WashPointForQueryListVO();
            washPointForQueryListVO.setId(x.getId());
            washPointForQueryListVO.setName(x.getName());
            washPointForQueryListVO.setConcreteList(x.getWashPointConcretePositionEntities().stream().map(x1 -> {
                WashPointPositionVO wpp = new WashPointPositionVO();
                wpp.setId(x1.getId());
                wpp.setBuilding(x1.getBuilding());
                wpp.setFloor(x1.getFloor());
                wpp.setRoom(x1.getRoom());
                return wpp;
            }).collect(Collectors.toList()));
            return washPointForQueryListVO;
        }).collect(Collectors.toList());
    }

    private Specification<WashPointEntity> assembleSpecification(String keyword, String operatorId) {
        Boolean viewAllData = AuthenticationHolder.getUserAuth().getViewAllData();
        return (root, query, criteriaBuilder) -> {
            List<Predicate> list = new ArrayList<>();
            if (!AuthenticationHolder.isAdmin()) {
                if (AuthenticationHolder.isAgent()) {
                    List<String> collect = userEntityRepo.findAllByCreatorId(AuthenticationHolder.getUserId()).stream()
                            .map(UserEntity::getId).collect(Collectors.toList());
                    list.add(root.get("merchantId").in(collect));
                } else {
                    if (viewAllData) {
                        String userId = AuthenticationHolder.getUserId();
                        UserEntity merchant = userRelService.getUserMerchant(userId);
                        list.add(criteriaBuilder.equal(root.get("merchantId"), merchant.getId()));
                    } else {
                        Set<String> allSub = userRelService.getDescendantIds(AuthenticationHolder.getUserId());
                        allSub.add(AuthenticationHolder.getUserId());
                        list.add(root.get("creatorId").in(allSub));
                    }
                }
            } else {
                if (!StringUtils.isBlank(operatorId)) {
                    list.add(criteriaBuilder.equal(root.get("merchantId"), operatorId));
                }
            }
            if (!StringUtils.isBlank(keyword)) {
                List<Predicate> likes = new ArrayList<>();
                likes.add(criteriaBuilder.like(root.get("name"), "%" + keyword + "%"));
                if (keyword.indexOf("市") > 0) {
                    likes.add(criteriaBuilder.like(root.get("area"), "%" + keyword.substring(keyword.indexOf("市") + 1, keyword.length()) + "%"));
                } else {
                    likes.add(criteriaBuilder.like(root.get("area"), "%" + keyword + "%"));
                }
                likes.add(criteriaBuilder.like(root.get("belongMerchantName"), "%" + keyword + "%"));
                Predicate[] predicates = new Predicate[likes.size()];
                list.add(criteriaBuilder.or(likes.toArray(predicates)));
            }
            Predicate[] pre = new Predicate[list.size()];
            return criteriaBuilder.and(list.toArray(pre));
        };
    }

    private String computeMonthGain(String washPointId) {

        StringBuilder sb = new StringBuilder("select sum(o.price) from wash_point wp left join " +
                " device_order o on o.wash_point_id = wp.id WHERE wp.id = :washPointId AND o.create_time >= :startTime");
        Query nativeQuery = entityManager.createNativeQuery(sb.toString());
        nativeQuery.setParameter("washPointId", washPointId);
        nativeQuery.setParameter("startTime", getFistDayTime());
        Integer totalMonthGain = Optional.ofNullable(QueryUtil.getSingleResult(nativeQuery)).map(x -> {
            return Integer.parseInt(x.toString());
        }).orElse(0);

        return AmountUtil.amountRoundStr(totalMonthGain, 2);
    }

    private Long getFistDayTime() {
        LocalDateTime of = LocalDateTime.of(LocalDateTime.now().getYear(), LocalDateTime.now().getMonth(), 1, 0, 0);
        return of.toInstant(ZoneOffset.of("+8")).toEpochMilli();
    }

    @Override
    @Transactional
    public void deleteWashPoint(String washPointId) {
        Optional<WashPointEntity> washPoint = washPointEntityRepo.findById(washPointId);
        if (!washPoint.isPresent()) {
            throw new BusinessException(Code.ERROR, "没有id为" + washPointId + "的洗衣点！");
        }
        if (CollectionUtils.isNotEmpty(deviceEntityRepo.findAllByWashPointId(washPointId))) {
            throw new BusinessException(Code.ERROR, "该洗衣点下有关联设备，无法删除！");
        }
        washPointEntityRepo.delete(washPoint.get());
    }

    @Override
    public WashPointDetailVO queryWashPointDetail(String washPointId) {
        Optional<WashPointEntity> byId = washPointEntityRepo.findById(washPointId);
        if (!byId.isPresent()) {
            throw new BusinessException(Code.ERROR, "没有id为" + washPointId + "的洗衣点！");
        }
        WashPointEntity washPointEntity = byId.get();
        WashPointDetailVO copy = BeanCopyUtil.copy(washPointEntity, WashPointDetailVO.class);
        copy.setWashPointList(washPointEntity.getWashPointConcretePositionEntities().stream().map(x -> {
            WashPointPositionVO wpp = new WashPointPositionVO();
            wpp.setId(x.getId());
            wpp.setBuilding(x.getBuilding());
            wpp.setFloor(x.getFloor());
            wpp.setRoom(x.getRoom());
            return wpp;
        }).collect(Collectors.toList()));
        return copy;
    }

    @Override
    public List<WashPointExportVO> exportWashPoint(Set<String> washPointIds, String keyword, Boolean all, String userId) {
        if (CollectionUtils.isNotEmpty(washPointIds)) {
            if (AuthenticationHolder.isAdmin()) {
                return mapExport(washPointEntityRepo.findAllById(washPointIds));
            } else {
                Boolean viewAllData = AuthenticationHolder.getUserAuth().getViewAllData();
                String currentId = AuthenticationHolder.getUserId();
                if (viewAllData) {
                    UserEntity merchant = userRelService.getUserMerchant(currentId);
                    return mapExport(washPointEntityRepo.findAllByMerchantIdAndIdIn(merchant.getId(), washPointIds));
                } else {
                    Set<String> allSub = userRelService.getDescendantIds(currentId);
                    allSub.add(currentId);
                    return mapExport(washPointEntityRepo.findAllByCreatorIdInAndIdIn(allSub, washPointIds));
                }

            }
        } else if (StringUtils.isNotBlank(keyword)) {
            return mapExport(washPointEntityRepo.findAll(assembleSpecification(keyword, userId)));
        } else {
            return mapExport(washPointEntityRepo.findAll(assembleSpecification("", userId)));
        }
    }

    @Override
    public UserPickWashPointListVO getUserPickList(WashPointType type,
                                                   String longitude,
                                                   String latitude) {
        UserPickWashPointListVO re = new UserPickWashPointListVO();
        String preferWashPointId = userPreferWashPointService.getPreferWashPointId(AuthenticationHolder.getUserId());

        if (StringUtils.isNotBlank(preferWashPointId)) {
            WashPointEntity washPointEntity = washPointEntityRepo.findById(preferWashPointId).orElse(null);
            if (washPointEntity != null) {
                boolean isPre = false;
                if (WashPointType.SELF_WASH_CLOTH.equals(type)) {
                    if (CollectionUtil.isNotEmpty(deviceEntityRepo.findByWashPointIdAndProductTypeAndOnline(washPointEntity.getId(), ProductType.WASHING_MACHINE, true))) {
                        isPre = true;
                    }
                } else if (WashPointType.SELF_DRY_CLOTH.equals(type)) {
                    if (CollectionUtil.isNotEmpty(deviceEntityRepo.findByWashPointIdAndProductTypeAndOnline(washPointEntity.getId(), ProductType.DRY_MACHINE, true))) {
                        isPre = true;
                    }
                } else {
                    if (CollectionUtil.isNotEmpty(deviceEntityRepo.findByWashPointIdAndProductTypeAndOnline(washPointEntity.getId(), ProductType.WASHING_SHOES_MACHINE, true))) {
                        isPre = true;
                    }
                }
                if (isPre) {
                    double dis = GeoDisComputeUtil.computeDistance(Double.valueOf(washPointEntity.getLatitude()),
                            Double.valueOf(washPointEntity.getLongitude()),
                            Double.valueOf(latitude),
                            Double.valueOf(longitude));
                    UserPickWashPointListVO.Info info = new UserPickWashPointListVO.Info(washPointEntity.getId(), washPointEntity.getName(), dis,
                            washPointEntity.getLongitude(), washPointEntity.getLatitude());
                    re.setPreferWashPoint(info);
                }
            }
        }

        List<WashPointEntity> allWashPoint = washPointEntityRepo.findAll();
        List<UserPickWashPointListVO.Info> peekList = allWashPoint.stream().filter(x -> {
            if (StringUtils.isBlank(x.getLatitude()) || StringUtils.isBlank(x.getLongitude())) {
                return false;
            }
            if (WashPointType.SELF_WASH_CLOTH.equals(type)) {
                return CollectionUtil.isNotEmpty(deviceEntityRepo.findByWashPointIdAndProductTypeAndOnline(x.getId(), ProductType.WASHING_MACHINE, true));
            } else if (WashPointType.SELF_DRY_CLOTH.equals(type)) {
                return CollectionUtil.isNotEmpty(deviceEntityRepo.findByWashPointIdAndProductTypeAndOnline(x.getId(), ProductType.DRY_MACHINE, true));
            } else {
                return CollectionUtil.isNotEmpty(deviceEntityRepo.findByWashPointIdAndProductTypeAndOnline(x.getId(), ProductType.WASHING_SHOES_MACHINE, true));
            }
        }).map(x -> {
            UserPickWashPointListVO.Info r = new UserPickWashPointListVO.Info();
            r.setId(x.getId());
            r.setName(x.getName());
            r.setLatitude(x.getLatitude());
            r.setLongitude(x.getLongitude());
            r.setDistance(GeoDisComputeUtil.computeDistance(Double.valueOf(x.getLatitude()),
                    Double.valueOf(x.getLongitude()),
                    Double.valueOf(latitude),
                    Double.valueOf(longitude)));
            return r;
        }).collect(Collectors.toList());
        List<UserPickWashPointListVO.Info> result = peekList.stream()
                .sorted(Comparator.comparing(UserPickWashPointListVO.Info::getDistance))
                .limit(3)
                .filter(x -> {
                    if (Objects.nonNull(re.getPreferWashPoint())) {
                        return !x.getId().equals(re.getPreferWashPoint().getId());
                    }
                    return true;
                })
                .collect(Collectors.toList());
        re.setList(result);
        return re;
    }

    /**
     * 当selectKey = 1 获取所有运营商的值；selectKey =2，获取所有场地名称的值
     * 当selectKey =2，value传归说运营商的id时，获得所有场地名称
     *
     * @param selectKey
     * @param value
     * @return
     */
    @Override
    public BaseResult getSelectInfo(String selectKey, String value) {
        Set<SelectInfoVo> selectInfoList = Sets.newHashSet();
        String merchantId = value;
        List<WashPointEntity> list;
        Set<String> allSub = new HashSet<>();
        // 传值为空时查询运营商下拉选
        if (StringUtils.isBlank(merchantId)) {
            if (AuthenticationHolder.isReplenish()) {//按照业务逻辑，补货员应该可以看到所属运营商能看到的内容
                UserEntity userEntity = userEntityRepo.findById(AuthenticationHolder.getUserId()).orElseThrow(() -> new BusinessException(Code.NODATA));
                // 获取所属商户Id
                merchantId = userEntity.getMerchantId();
            }// 运营商
            else if (AuthenticationHolder.isMerchant()) {
                Boolean viewAllData = AuthenticationHolder.getUserAuth().getViewAllData();
                if (viewAllData) {
                    UserEntity userEntity = userEntityRepo.findById(AuthenticationHolder.getUserId()).orElseThrow(() -> new BusinessException(Code.NODATA));
                    // 获取所属商户Id
                    merchantId = userEntity.getMerchantId();
                } else {
                    // 获取所有子账号id集合
                    allSub = userRelService.getDescendantIds(AuthenticationHolder.getUserId());
                    allSub.add(AuthenticationHolder.getUserId());
                    // 子账户情形下 给merchantId设置sub标识
                    merchantId = "sub";
                }
            }
        }
        if (StringUtils.isNotBlank(merchantId) && !"sub".equals(merchantId)) {
            list = washPointEntityRepo.findAllByMerchantId(merchantId);
        } else if (StringUtils.isNotBlank(merchantId) && "sub".equals(merchantId)) {
            list = washPointEntityRepo.findAllByCreatorIdIn(allSub);
        } else {
            list = washPointEntityRepo.findAll();
        }
        selectInfoList = build(selectInfoList, list, selectKey);
        return BaseResultFactory.produceSuccess(selectInfoList);
    }

    /**
     * @param selectInfoList 下拉选列别哦
     * @param list           查询数据
     * @param selectKey      1:运营商，2 场地名称
     * @return
     */
    private Set<SelectInfoVo> build(Set<SelectInfoVo> selectInfoList, List<?> list, String selectKey) {
        if (CollectionUtil.isEmpty(list)) {
            return selectInfoList;
        }
        if (list.get(0).getClass().isAssignableFrom(WashPointEntity.class)) {
            list.stream().forEach(a -> {
                WashPointEntity entity = (WashPointEntity) a;
                SelectInfoVo selectInfoVo = new SelectInfoVo();
                if (StringUtils.equals("1", selectKey)) {
                    selectInfoVo.setText(entity.getBelongMerchantName());
                    selectInfoVo.setValue(entity.getMerchantId());
                } else if (StringUtils.equals("2", selectKey)) {
                    selectInfoVo.setText(entity.getName());
                    selectInfoVo.setValue(entity.getId());
                }
                selectInfoList.add(selectInfoVo);
            });
        } else if (list.get(0).getClass().isAssignableFrom(WashPointConcretePositionEntity.class)) {
            StringBuffer sb = new StringBuffer();
            list.stream().forEach(a -> {
                sb.setLength(0);
                WashPointConcretePositionEntity entity = (WashPointConcretePositionEntity) a;
                SelectInfoVo selectInfoVo = new SelectInfoVo();
                selectInfoVo.setText(sb.append(entity.getBuilding())
                        .append("楼").append(entity.getFloor()).append("层").append(entity.getRoom()).append("室").toString());
                selectInfoVo.setValue(entity.getId());
                selectInfoList.add(selectInfoVo);
            });
        }
        return selectInfoList;
    }

    private List<WashPointExportVO> mapExport(List<WashPointEntity> list) {
        return list.stream().map(x -> {
            WashPointExportVO copy = BeanCopyUtil.copy(x, WashPointExportVO.class);
            copy.setDeviceCount(deviceEntityRepo.countByWashPointId(x.getId()));
            copy.setMountGain(computeMonthGain(x.getId()));
            return copy;
        }).collect(Collectors.toList());
    }

}
